package puzzle.akari.solver;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import puzzle.akari.data.AkariBoard;
import puzzle.akari.data.BlockNumber;
import puzzle.common.data.HatanException;
import puzzle.common.point.Direction;
import puzzle.common.point.Pnt;
import puzzle.common.solver.AbstractSolver;
import puzzle.common.solver.AbstractTopology;
import puzzle.common.solver.BoardSnapshot;
import puzzle.common.solver.PntAssumption;
import puzzle.common.solver.SolutionState;
import tomoto.util.IntHolder;

public class AkariSolver extends AbstractSolver<AkariBoard, AbstractTopology, PntAssumption> {

	public AkariSolver() {
		addRule(new PossibilityToBeLitRule());
		addRule(new NumberRule());
	}

	@Override
	protected AbstractTopology analyzeTopology(AkariBoard b) {
		// nothing to do
		return null;
	}

	@Override
	protected void applyAssumption(
			List<BoardSnapshot<AkariBoard, AbstractTopology, PntAssumption>> bss, AkariBoard b,
			PntAssumption a) throws HatanException {
		b.setLightPut(a.p);
		b.setAssumed(a.p);
	}

	@Override
	protected List<PntAssumption> assume(AkariBoard b, AbstractTopology topology,
			PntAssumption prevAssumption) {
		List<PntAssumption> assumptions1 = new ArrayList<PntAssumption>();
		List<PntAssumption> assumptions2 = new ArrayList<PntAssumption>();
		for (int y = 0; y < b.height; y++) {
			for (int x = 0; x < b.width; x++) {
				if (b.canLightBePut(x, y)) {
					PntAssumption a = new PntAssumption(new Pnt(x, y));
					(isNearNumber(b, x, y) ? assumptions1 : assumptions2).add(a);
				}
			}
		}
		assumptions1.addAll(assumptions2);
		return assumptions1;
	}

	private boolean isNearNumber(AkariBoard b, int x, int y) {
		for (Direction d : Direction.CLOCKWISE8) {
			if (BlockNumber.isNumber(b.getBlockNumber(x + d.v.dx, y + d.v.dy))) {
				return true;
			}
		}
		return false;
	}

	@Override
	protected SolutionState checkSolutionState(AkariBoard b, AbstractTopology topology) {
		IntHolder forbiddenCount = new IntHolder();
		IntHolder putCount = new IntHolder();
		boolean isComplete = true;

		for (int y = 0; y < b.height; y++) {
			for (int x = 0; x < b.width; x++) {
				int n = b.getBlockNumber(x, y);

				// all non-block cells must be lit
				if (BlockNumber.isNonBlock(n)) {
					if (b.isLit(x, y) || b.isLightPut(x, y)) {
						// OK
					} else {
						isComplete = false;
					}
				} else if (BlockNumber.isNumber(n)) {
					// all number blocks must be satisfied
					countAround(b, x, y, putCount, forbiddenCount);
					if (putCount.value > n || forbiddenCount.value > 4 - n) {
						return SolutionState.HATAN;
					} else if (putCount.value < n) {
						isComplete = false;
					}
				}
			}
		}

		return isComplete ? SolutionState.COMPLETE : SolutionState.INCOMPLETE;
	}

	private void countAround(AkariBoard b, int x, int y, IntHolder putCount,
			IntHolder forbiddenCount) {
		forbiddenCount.value = 0;
		putCount.value = 0;
		for (Direction d : Direction.CLOCKWISE4) {
			int x1 = x + d.v.dx;
			int y1 = y + d.v.dy;
			if (b.isLightForbidden(x1, y1)) forbiddenCount.value++;
			if (b.isLightPut(x1, y1)) putCount.value++;
		}
	}

	@Override
	protected void excludeAssumption(AkariBoard b, PntAssumption a) throws HatanException {
		b.setLightForbidden(a.p.x, a.p.y);
	}

	@Override
	protected boolean isAlreadyTried(
			List<BoardSnapshot<AkariBoard, AbstractTopology, PntAssumption>> bss, AkariBoard b,
			PntAssumption a) {
		int x = a.p.x;
		int y = a.p.y;

		if (b.isLightForbidden(x, y) || b.isLightPut(x, y)) {
			return true;
		}

		for (BoardSnapshot<AkariBoard, AbstractTopology, PntAssumption> bs : bss) {
			if (bs.getBoard().isLightPut(x, y)) {
				return true;
			}
		}

		return false;
	}

	@Override
	protected void sortBoardSnapshots(
			List<BoardSnapshot<AkariBoard, AbstractTopology, PntAssumption>> bss) {
		Collections.sort(bss, BOARD_COMPARATOR);
	}

	private static final Comparator<BoardSnapshot<AkariBoard, AbstractTopology, PntAssumption>> BOARD_COMPARATOR = new Comparator<BoardSnapshot<AkariBoard, AbstractTopology, PntAssumption>>() {
		public int compare(BoardSnapshot<AkariBoard, AbstractTopology, PntAssumption> o1,
				BoardSnapshot<AkariBoard, AbstractTopology, PntAssumption> o2) {
			return o1.getBoard().getNumberOfDarkCells() - o2.getBoard().getNumberOfDarkCells();
		}
	};

}
